Skip to content

feat(auth): Implement secure httpOnly cookie authentication for Okta#1920

Open
arjunp99 wants to merge 19 commits intodata-dot-all:mainfrom
arjunp99:feature/secure-httponly-cookie-auth
Open

feat(auth): Implement secure httpOnly cookie authentication for Okta#1920
arjunp99 wants to merge 19 commits intodata-dot-all:mainfrom
arjunp99:feature/secure-httponly-cookie-auth

Conversation

@arjunp99
Copy link
Contributor

@arjunp99 arjunp99 commented Mar 3, 2026

Summary

Replace localStorage token storage with httpOnly cookies to prevent XSS attacks for Okta authentication. This implements a custom PKCE flow while maintaining existing Cognito/Amplify behavior unchanged.

Security Improvements

  • Tokens stored in httpOnly cookies (not accessible via JavaScript - prevents XSS token theft)
  • SameSite=Lax prevents CSRF while allowing OAuth redirects from Okta
  • Secure flag ensures HTTPS-only transmission

Changes

Frontend

  • frontend/src/utils/pkce.js - PKCE utility for secure OAuth code exchange
  • frontend/src/authentication/views/Callback.js - OAuth callback handler
  • frontend/src/authentication/contexts/GenericAuthContext.js - Cookie-based auth for Okta
  • frontend/src/services/hooks/useClient.js - Relative URLs + credentials for cookies
  • frontend/src/routes.js - Added /callback route

Backend

  • backend/auth_handler.py - Token exchange, userinfo, logout endpoints
  • deploy/stacks/lambda_api.py - Auth handler Lambda + API routes
  • deploy/stacks/cloudfront.py - Proxy /auth/, /graphql/, /search/* to API Gateway
  • deploy/custom_resources/custom_authorizer/custom_authorizer_lambda.py - Read tokens from Cookie header

How It Works

  1. User clicks login → redirected to Okta with PKCE challenge
  2. Okta redirects back to /callback with authorization code
  3. Frontend calls /auth/token-exchange with code + PKCE verifier
  4. Backend exchanges code for tokens, sets httpOnly cookies
  5. All subsequent API calls include cookies automatically (same-origin via CloudFront proxy)
  6. Custom authorizer reads access_token from Cookie header

Backward Compatibility

  • Cognito users: No changes - continues using Amplify with Authorization header

arjunp99 added 3 commits March 3, 2026 11:00
Replace localStorage token storage with httpOnly cookies to prevent XSS
attacks. Implements custom PKCE flow for Okta authentication while
maintaining existing Cognito/Amplify behavior unchanged.

Changes:
- Add PKCE utility for secure OAuth code exchange
- Add Callback view for handling OAuth redirects
- Add backend auth_handler for token exchange endpoints
- Update GenericAuthContext with cookie-based auth for Okta
- Update useClient to work without Authorization header for Okta
- Configure CloudFront to proxy /auth/*, /graphql/*, /search/* paths
- Update Lambda API with auth endpoints and CORS for cookies
- Update custom authorizer to read tokens from Cookie header

Security improvements:
- Tokens stored in httpOnly cookies (not accessible via JavaScript)
- SameSite=Lax prevents CSRF while allowing OAuth redirects
- Secure flag ensures HTTPS-only transmission
Security improvements:
- Add structured logging with sanitized error messages
- Remove hardcoded CloudFront URL fallback (requires proper config)
- Move SimpleCookie import to module level for better performance

Frontend enhancements:
- Add 30-second timeout to token exchange requests
- Fix useEffect dependency array in useClient hook
- Implement OAuth callback handler with PKCE validation

Infrastructure updates:
- Configure auth handler Lambda for cookie-based authentication
- Add API Gateway routes for token exchange, logout, and userinfo
- Improve CloudFront URL parsing documentation

All changes pass Ruff linting and formatting checks.
def logout_handler(event):
"""Clear all auth cookies"""
cookies = []
for cookie_name in ['access_token', 'id_token', 'refresh_token']:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also logout from okta. Here we are deleting those cookies but that doesn't mean we will be logging out of Okta. Should we make a call to the okta endpoint to let Okta know that we want to logout ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good point! I'll implement Okta logout by redirecting to Okta's /v1/logout endpoint from the frontend after clearing cookies. This fully ends the Okta session so the user must re-authenticate on next login.

The flow will be:

Frontend calls /auth/logout to clear cookies
Frontend redirects to {okta_url}/v1/logout?id_token_hint=...&post_logout_redirect_uri=...
This is handled on the frontend side since it requires a browser redirect.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super nit: can you comment this flow as a comment here so that anyone looking at this will have clear reference

Copy link
Collaborator

@TejasRGitHub TejasRGitHub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @arjunp99 , The changes made by you look very solid. I have added comments some of which are for clarification and some cosmetic. But mostly everything looks solid

There are a few things which I think are missing,

  1. What happens when the user token expires, in the current implementation, the webapp automatically resets to the login page. There is an internal event set when the token reaches expiration. I think if possible we should mimick that.
  2. The logout flow currently only clears the cookies but it should also logout from okta if such an endpoint is present to invalid the tokens in okta at the same time user logouts

@TejasRGitHub TejasRGitHub requested a review from petrkalos March 4, 2026 22:59
arjunp99 added 3 commits March 9, 2026 10:21
## Changes

### Backend (auth_handler.py)
- Add binascii import for proper base64 decoding error handling
- Improve 404 error message to list valid auth endpoints
- Add CLOUDFRONT_URL validation with debug logging
- Add detailed comments explaining JWT payload decoding logic
- Add specific exception handling for JWT decode errors (binascii.Error, ValueError, JSONDecodeError)
- Separate generic exceptions from token-specific errors for better debugging

### Infrastructure
- deploy/stacks/cloudfront.py: Remove unnecessary backend_region check (custom_auth is sufficient)
- deploy/stacks/lambda_api.py: Use PYTHON_LAMBDA_RUNTIME constant instead of hardcoded Python 3.12
- deploy/stacks/lambda_api.py: Add comment clarifying authorizer supports both cookie and Authorization header auth

### Frontend
- frontend/src/authentication/contexts/GenericAuthContext.js: Import PKCE utils from centralized utils/index.js
- frontend/src/utils/index.js: Export PKCE utilities for better code organization
- frontend/src/index.js: Minor formatting cleanup

## Benefits
- Better error messages for debugging authentication issues
- Improved code maintainability with centralized imports
- More robust JWT decoding with specific error handling
- Clearer documentation for dual authentication support (cookies + headers)
- Backend logout_handler returns Okta logout URL with id_token_hint
- Frontend redirects to Okta /v1/logout to end SSO session
- Added cloudfront_url config fallback for CLOUDFRONT_URL env var
## Checkov Fixes (5 issues resolved)

### 1. CKV_AWS_158: CloudWatch Log Group KMS Encryption ✓
- Add dedicated KMS key (logs_kms_key) for CloudWatch Logs encryption
- Configure key policy to allow CloudWatch Logs service access
- Apply encryption to auth handler log group
- Enable key rotation for compliance

### 2. CKV_AWS_115: Lambda Concurrent Execution Limit ✓
- Set reserved_concurrent_executions=100 for auth handler Lambda
- Prevents runaway costs from unlimited concurrent executions
- Provides predictable capacity planning
- Protects against DDoS-style attacks

### 3-5. CKV_AWS_59: API Gateway Authorization ✓
- Add suppression comments for 3 auth endpoints:
  * /auth/token-exchange (POST)
  * /auth/logout (POST)
  * /auth/userinfo (GET)
- These endpoints are intentionally public by design for OAuth flow
- Protected by multiple security layers:
  * WAF rules (rate limiting, IP filtering)
  * CORS restrictions (only allowed origins)
  * Lambda-side validation (cookie/token validation)
- OAuth callback endpoint must be publicly accessible
- Logout must work with expired tokens
- Userinfo validates cookies internally before returning data

## Security Posture
- Auth handler logs now encrypted at rest with KMS (AES-256)
- Key rotation enabled for compliance
- Lambda concurrency limited to prevent cost overruns
- Public auth endpoints documented and justified
- Defense-in-depth approach maintained (WAF + CORS + Lambda validation)

## Impact
- No breaking changes
- All new resources properly secured
- Passes all Checkov security scans for new code
custom_waf_rules=None,
tooling_account_id=None,
backend_region=None,
custom_auth=None,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass this same via the cloudfront_stack . It's not gettign picked up and is None leading to no deployment of the behaviours


# Initialize Klayers
klayers = Klayers(self, python_version=PYTHON_LAMBDA_RUNTIME, region=self.region)
runtime = _lambda.Runtime.PYTHON_3_9
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Runtime is using incorrect version of python lamnda. instead should use PYTHON_LAMBDA_RUNTIME

arjunp99 added 8 commits March 9, 2026 14:43
- Fix ruff formatting in deploy/stacks/lambda_api.py
- Replace hardcoded PYTHON_3_9 with PYTHON_LAMBDA_RUNTIME constant
- Fix ESLint/Prettier errors in frontend (Callback.js, index.js, pkce.js)
- Run npm audit fix for safe vulnerability patches
…erability

Update serialize-javascript from 6.0.2 to ^7.0.3 to address GHSA-5c6j-r48x-rmvq
(CVE-2020-7660 incomplete fix). The vulnerability allows RCE via RegExp.flags
and Date.prototype.toISOString() when attacker controls input to serialize().

Changes:
- Add serialize-javascript ^7.0.3 as direct dependency
- Add top-level override for serialize-javascript
- Add nested override for rollup-plugin-terser dependency
- Add resolution for yarn compatibility
- Regenerate lock files
Add auth API Gateway methods to checkov baseline. These endpoints
intentionally have no API Gateway authorization because they are
part of the OAuth authentication flow:

- /auth/token-exchange - exchanges authorization code for tokens
- /auth/logout - clears httpOnly cookies
- /auth/userinfo - returns user info from validated cookies

Security is enforced at the Lambda level through:
- CORS restrictions to CloudFront domain
- Cookie validation and Okta token verification
- WAF rules (when configured)

Also baseline the new AuthHandler Lambda function for standard
Lambda checks (CKV_AWS_115, CKV_AWS_116, CKV_AWS_158).
…lity

The latest react-apexcharts (1.4.x+) requires apexcharts>=4.0.0 which
conflicts with the project's apexcharts@^3.33.2. Pin to 1.3.9 which
supports apexcharts@^3.18.0.
- Backend: Check token expiration in userinfo endpoint and return exp claim
- Frontend: Set up timer to auto-logout when token expires (with 30s buffer)
- Clear timer on logout and component unmount
- Mimics Cognito's automatic session expiration behavior
- Add nosemgrep comment to suppress urllib false positive in auth_handler
- Add new logout endpoint resource ID to checkov baseline
- Add underscore resolution to fix npm-audit vulnerability
- Regenerate package-lock.json to sync with package.json
Extract exp claim from id_token and use it for cookie max_age instead
of hardcoded 1 hour. This ensures cookie lifetime matches Okta's token
settings (30 min for Prod, 1 hour for Dev).
Validate that CUSTOM_AUTH_URL uses https:// scheme before making
requests to prevent file:// or other dangerous schemes (Bandit B310).
import PropTypes from 'prop-types';
import { useAuth } from 'react-oidc-context';
import {
fetchAuthSession,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @arjunp99 , why are these functions removed from here? Lets keep the same flow and functions as it was previously

I am getting errors with the direct import

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ERROR in ./src/authentication/contexts/GenericAuthContext.js 165:25-54
export 'Auth' (imported as 'Auth') was not found in 'aws-amplify' (possible exports: Amplify)

Explanation

This happened because the PR's GenericAuthContext.js used the Amplify v5 API (import { Auth } from 'aws-amplify'), but package.json specifies aws-amplify@^6.15.8. In Amplify v6, Auth was removed from the top-level aws-amplify export — the only top-level export is Amplify. Auth functions moved to aws-amplify/auth as individual named exports (fetchAuthSession, fetchUserAttributes, signInWithRedirect, signOut, etc.).
Your awsdeploy branch already had the correct v6 imports, but git's auto-merge picked the PR's v5-style code, which overwrote your working v6 code. We fixed it by restoring the v6 API calls matching what you had before (and what your reference file showed).

- Update GenericAuthContext.js to use Amplify v6 imports from
  'aws-amplify/auth' instead of deprecated v5 'Auth' from 'aws-amplify'
- Replace Auth.currentAuthenticatedUser() with fetchAuthSession() and
  fetchUserAttributes()
- Replace Auth.federatedSignIn() with signInWithRedirect()
- Replace Auth.signOut() with signOut()
- Add /auth/userinfo call in Callback.js after token exchange to verify
  cookies are set before redirecting
- Use window.location.href instead of navigate() for full page reload
  to ensure auth context re-initializes with new cookies
await signInWithRedirect();
}
} catch (error) {
if (error.name === 'UserAlreadyAuthenticatedException') {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this not needed ?

user: null
}
});
sessionStorage.removeItem('window-location');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why has the location changed for this one ?

@TejasRGitHub
Copy link
Collaborator

TejasRGitHub commented Mar 16, 2026

Testing :

  1. Merge and deploy to AWS account -> ✅
  2. Codepipeline successful with no issues ✅
  3. User able to login ✅ ( Please check the notable changes made for this to work )
  4. User able to logout ✅
  5. Auth tokens stored in cookies with httpOnly and secure ✅
  6. Auth tokens deleted when logout ✅
  7. ReAuth workflow when deleting dataset / envrionment ❌
  8. Auto logout when the session reaches expiration ❌
    During the session expiry, the user tokens from the cookies are removed but the data.all FE is not directed to the login page
image

Miscellaneous :

  1. Incorrect expires / max-age . Currently it is showing issuing time
image

Notable changes

  1. The redirect URI on the okta app as well as on the cdk.json file had to be changed to include the /callback

- Add handleSessionExpired() function for proper session expiration handling
- When token expires, redirect to homepage (login page) instead of staying
  on current page showing 403 errors
- Fix reauth to clear cookies without redirecting to Okta logout, then
  trigger new login flow so user can re-authenticate via SSO seamlessly
- Clear sessionStorage on both session expiry and reauth
Add overrides and resolutions for transitive dependency vulnerabilities:
- flatted ^3.4.2 (CVE-1114526: DoS via unbounded recursion)
- serialize-javascript ^7.0.4 (CVE-1113686: RCE vulnerability)
- svgo ^2.8.1 (CVE-1114152: DoS via entity expansion)
- fast-xml-parser 5.5.6 (CVE-1114772: numeric entity expansion bypass)

These are pre-existing vulnerabilities in react-scripts and eslint
transitive dependencies, not caused by this PR's changes.
- Logout: Use silent logout (clear cookies only) without redirecting
  to Okta logout endpoint. Matches previous signoutSilent() behavior.
  No Okta Sign-out redirect URI configuration needed.

- ReAuth: Add zIndex 9999 to ReAuth modal so it appears on top of
  other dialogs like delete confirmation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants